#ifndef CIRCULAR_BUFFER_H_
#define CIRCULAR_BUFFER_H_
#include <array>
#include <cstdint>

// circular buffer
template <class T, std::uint32_t size>
class CircularBuffer {
 public:
  CircularBuffer() noexcept : head_{0U}, rear_{0U}, number_filled_{0} {}
  ~CircularBuffer() = default;
  CircularBuffer(CircularBuffer const&) = delete;
  CircularBuffer& operator=(CircularBuffer const&) = delete;
  CircularBuffer(CircularBuffer&&) = delete;
  CircularBuffer& operator=(CircularBuffer&&) = delete;

  ///@brief  buffer is full
  ///@return bool, if full true, else false
  bool IsFull() const noexcept {
    if (number_filled_ >= size) {
      return true;
    } else {
      return false;
    }
  }

  std::uint32_t GetNumberFilled() const noexcept { return number_filled_; }

  std::uint32_t Size() const noexcept { return size; }

  ///@brief buffer is empty
  ///@return bool, if empty true,else false
  bool IsEmpty() const noexcept {
    if (number_filled_ <= 0) {
      return true;
    } else {
      return false;
    }
  }

  ///@brief get next member reference
  ///@return next member reference
  T& GetNext() noexcept {
    if (number_filled_ < size) {
      rear_ = (rear_ + 1) % data_array_.size();
    }
    return data_array_[rear_];
  }

  ///@brief rear position move forward
  ///@throw std::runtime_error buffer size is 0
  void Push() noexcept(false) {
    if (size <= 0) {
      throw std::runtime_error("buffer size is 0");
    }
    if (number_filled_ < size) {
      number_filled_++;
    }
  }

  ///@brief rear position move forward
  ///@throw std::runtime_error buffer size is 0
  void Push(T element) noexcept(false) {
    if (size <= 0) {
      throw std::runtime_error("buffer size is 0");
    }
    if (number_filled_ < size) {
      rear_ = (rear_ + 1) % data_array_.size();
      data_array_[rear_] = element;
      number_filled_++;
    } else {
      throw std::runtime_error("buffer is full");
    }
  }

  ///@brief pop header member reference and head position move forward
  ///@return header member reference
  ///@throw std::runtime_error buffer size is 0
  T const& Pop() noexcept(false) {
    if (size <= 0) {
      throw std::runtime_error("buffer size is 0");
    }
    if (number_filled_ > 0) {
      head_ = (head_ + 1) % data_array_.size();
      number_filled_--;
      return data_array_[head_];
    } else {
      throw std::runtime_error("number filled is 0");
    }
  }

  ///@brief Clear objects
  void Clear() noexcept {
    head_ = 0U;
    rear_ = 0U;
    number_filled_ = 0U;
  }

 private:
  ///@brief Queue
  std::array<T, size + 1> data_array_;
  ///@brief head index
  std::uint32_t head_;
  ///@brief rear index
  std::uint32_t rear_;
  ///@brief number filled
  std::uint32_t number_filled_;
};

#endif  // !CIRCULAR_BUFFER_H_
